Перейти к основному содержимому

8. React

React — это JavaScript-библиотека с открытым исходным кодом, разработанная Facebook для создания пользовательских интерфейсов. Основная идея React — это построение интерфейса из небольших независимых компонентов, которые затем объединяются в сложные приложения. В этом разделе мы рассмотрим основы React и его практическое применение в проекте React-приложения медицинской клиники.

История и эволюция React

React был создан Джорданом Уолком, инженером Facebook, и впервые использовался для ленты новостей Facebook в 2011 году. Официально библиотека была выпущена с открытым исходным кодом в мае 2013 года. React является одной из самых популярных библиотек для создания пользовательских интерфейсов в мире, активно развивается и поддерживается сообществом разработчиков. React позволяет создавать пользовательские интерфейсы, которые могут быть использованы как на серверной, так и на клиентской стороне.

Ключевые вехи в истории React:

  • 2013 — Первый публичный релиз React
  • 2015 — Выпуск React Native для мобильной разработки
  • 2016 — Выпуск React 15 с улучшенной поддержкой рендеринга
  • 2017 — Полное переписывание ядра React (React Fiber)
  • 2019 — Выпуск React 16.8 с хуками (Hooks)
  • 2022 — Выпуск React 18 с конкурентным режимом рендеринга

С каждым новым релизом React развивается в направлении улучшенной производительности, упрощения API и предоставления более декларативных способов создания пользовательских интерфейсов.

Ключевые концепции React на примере React-приложения медицинской клиники

Рассмотрим основные концепции React на примере автоматически сгенерированного приложения медицинской клиники, которое демонстрирует современный подход к разработке с использованием TypeScript, GraphQL и Apollo Client.

1. Функциональные компоненты и TypeScript

React-приложение медицинской клиники построено исключительно на функциональных компонентах с использованием TypeScript для строгой типизации. Функциональные компоненты представляют собой JavaScript-функции, которые принимают объект props в качестве аргумента и возвращают JSX-элементы.

Преимущества функциональных компонентов:

  • Простота и читаемость: Функциональные компоненты более лаконичны и легче для понимания
  • Производительность: Меньше накладных расходов по сравнению с классовыми компонентами
  • Хуки: Полная поддержка React Hooks для управления состоянием и побочными эффектами
  • TypeScript интеграция: Отличная совместимость с системой типов TypeScript

В приложении медицинской клиники TypeScript обеспечивает безопасность типов на этапе компиляции, автодополнение в IDE и раннее обнаружение ошибок. Каждый компонент явно типизирован с помощью React.FC<Props>, что гарантирует корректность передаваемых параметров.

// src/App.tsx
export const App: React.FC = () => {
const [apolloClient, setAppoloClient] = useState<ApolloClient<NormalizedCacheObject>>()

const initClient = async () => {
if (process.env.NODE_ENV === 'production')
await initEnv()

if (!apolloClient) {
return new ApolloClient({
cache: cache,
uri: process.env.NODE_ENV === 'production' ? process.env.DS_ENDPOINT : '/graphql',
})
}
}

2. Хуки React для управления состоянием

Приложение активно использует хуки React для управления локальным состоянием компонентов. Хуки — это функции, которые позволяют "подцепиться" к возможностям React (состояние, контекст, побочные эффекты) из функциональных компонентов.

Основные хуки, используемые в приложении:

  • useState: Управляет локальным состоянием компонента, возвращает текущее значение состояния и функцию для его обновления
  • useEffect: Выполняет побочные эффекты (запросы к API, подписки, таймеры) и может очищать ресурсы при размонтировании компонента
  • Пользовательские хуки: Apollo Client предоставляет специализированные хуки для работы с GraphQL запросами

Хуки обеспечивают:

  • Переиспользование логики: Состояние и эффекты можно выносить в отдельные функции
  • Предсказуемость: Хуки вызываются в одном и том же порядке при каждом рендере
  • Простоту тестирования: Логику состояния легче изолировать и протестировать

В примере useState управляет состоянием Apollo Client, а useEffect инициализирует клиент при монтировании компонента.

// src/App.tsx
const [apolloClient, setAppoloClient] = useState<ApolloClient<NormalizedCacheObject>>()

useEffect(() => {
const appoloClientInit = async () => {
const apolloClient = await initClient()
setAppoloClient(apolloClient)
}

appoloClientInit()
}, [])

3. Интеграция с GraphQL через Apollo Client

Приложение демонстрирует современный подход к работе с API через GraphQL и Apollo Client. GraphQL — это язык запросов и среда выполнения для API, которая позволяет клиентам запрашивать именно те данные, которые им нужны. Apollo Client — это комплексная библиотека для управления данными, которая упрощает получение, кэширование и обновление данных в React-приложениях.

Преимущества GraphQL интеграции в приложении:

  • Строгая типизация: Код-генерация создает TypeScript типы на основе GraphQL схемы
  • Автоматические хуки: Каждый GraphQL запрос становится готовым к использованию React хуком
  • Умное кэширование: Apollo Client автоматически кэширует результаты запросов и обновляет UI
  • Оптимистичные обновления: Возможность обновлять UI до получения ответа от сервера
  • Управление ошибками: Централизованная обработка сетевых ошибок и ошибок GraphQL

Процесс интеграции включает:

  1. Схема GraphQL определяет доступные операции и типы данных
  2. Код-генерация создает типизированные хуки для каждого запроса
  3. Apollo Client обеспечивает выполнение запросов и кэширование
  4. React компоненты используют сгенерированные хуки для получения данных

В примере useSearchDoctorQuery — это автоматически сгенерированный хук, который обеспечивает типобезопасный доступ к данным о врачах с поддержкой загрузки и обработки ошибок.

// src/components/__generate/Doctor/Doctor/DoctorList.tsx
import { useSearchDoctorQuery } from '../../../../__generate/graphql-frontend'

const { data, loading, error: queryError } = useSearchDoctorQuery(
{
variables: {
cond: (filterStr && filterStr !== 'undefined' && selectedDoctor !== null ) ? `it.${extractParamName(filterStr)}.id=='${extractParamValue(filterStr)}'` : null
}
})

4. Компонентная архитектура и композиция

Приложение следует принципам компонентной архитектуры, где сложные интерфейсы строятся из простых, переиспользуемых компонентов. Компонентная архитектура — это подход к разработке, при котором пользовательский интерфейс разбивается на независимые, переиспользуемые части, каждая из которых инкапсулирует свою логику и представление.

Ключевые принципы компонентной архитектуры в приложении:

  • Единственная ответственность: Каждый компонент выполняет одну четко определенную задачу
  • Переиспользование: Компоненты можно использовать в разных частях приложения
  • Композиция над наследованием: Сложное поведение достигается комбинированием простых компонентов
  • Инкапсуляция: Внутренняя логика компонента скрыта от внешнего мира

Иерархия компонентов в приложении:

  1. Layout компоненты: MainMenu, Header, Sider — определяют общую структуру
  2. Страничные компоненты: DoctorAgg, ClinicAgg — организуют логические разделы
  3. Функциональные компоненты: DoctorList, DoctorCreate — реализуют конкретную бизнес-логику
  4. UI компоненты: Button, Input, Table — базовые элементы интерфейса

React Router используется для декларативной навигации, где маршруты определяются как компоненты и могут быть вложенными. Это обеспечивает:

  • Декларативную маршрутизацию: Структура URL отражает иерархию компонентов
  • Ленивую загрузку: Компоненты загружаются только при необходимости
  • Программную навигацию: Возможность навигации через JavaScript код

В примере MainMenu демонстрирует композицию Layout, Menu и Routes компонентов для создания навигационной структуры приложения.

// src/components/__generate/MainMenu.tsx
export const MainMenu: FC = () => {
return (
<BrowserRouter>
<Layout>
<Header>
<Menu mode='horizontal' >
<Menu.Item key='DoctorAgg'>
<Link to={'/DoctorAgg'}>DoctorAgg</Link>
</Menu.Item>
// ... другие пункты меню
</Menu>
</Header>
<Routes>
<Route key={"Home"} path="/" element={<Home />}>
<Route key={"DoctorAgg"} path="DoctorAgg" element={<DoctorAgg />}>
{DoctorAggRouteList()}
</Route>
</Route>
</Routes>
</Layout>
</BrowserRouter>
)
}

5. Управление формами с контролируемыми компонентами

Формы в приложении реализованы как контролируемые компоненты, где состояние формы управляется через props и обратные вызовы, обеспечивая предсказуемое поведение. Контролируемые компоненты — это паттерн в React, при котором значения элементов формы хранятся в состоянии React компонента и обновляются через обработчики событий.

Принципы работы контролируемых компонентов:

  • Единственный источник истины: Состояние формы хранится в React состоянии
  • Контролируемый поток данных: Все изменения проходят через React обработчики
  • Валидация в реальном времени: Можно проверять данные при каждом изменении
  • Предсказуемость: Состояние формы всегда синхронизировано с UI

Преимущества контролируемых форм:

  • Полный контроль: Возможность программно изменять значения полей
  • Валидация: Проверка данных на каждом шаге ввода
  • Условная логика: Динамическое показ/скрытие полей на основе других значений
  • Интеграция с состоянием: Легкая интеграция с глобальным состоянием приложения

Архитектура форм в приложении:

  1. Родительский компонент (DoctorCreate) управляет состоянием всей формы
  2. Форма-компонент (DoctorCreateForm) получает данные через props и отправляет изменения через callback
  3. Отдельные поля связаны с конкретными свойствами состояния через value и onChange

Паттерн обратного вызова changeInputParameters обеспечивает обновление состояния в родительском компоненте при изменении любого поля формы. Это создает надежный поток данных и упрощает валидацию и сохранение данных.

// src/components/__generate/Doctor/Doctor/DoctorCreateForm.tsx
export const DoctorCreateForm: FC<FormProps> = ({ inputParameters, changeInputParameters }) => {
return (
<Form>
<Form.Item label="doctorType">
<Space>
<Input
value={inputParameters.doctorType}
onChange={e => changeInputParameters({ doctorType: e.target.value})}
/>
<Button onClick={() => setSelectedDoctorTypeId(null)}>{"..."}</Button>
</Space>
</Form.Item>
<Form.Item label="id">
<Input
value={inputParameters.id!}
onChange={e => changeInputParameters({ id: e.target.value })}
/>
</Form.Item>
</Form>
)
}

6. Декларативный рендеринг и условная отрисовка

React позволяет декларативно описывать пользовательский интерфейс. Декларативный подход означает, что разработчик описывает что должно быть отображено в зависимости от текущего состояния, а не как манипулировать DOM для достижения желаемого результата. Это противоположность императивному подходу, где необходимо явно указывать каждый шаг изменения интерфейса.

Принципы декларативного рендеринга:

  • Состояние как единственный источник истины: UI является функцией от состояния приложения
  • Автоматическое обновление: React автоматически обновляет DOM при изменении состояния
  • Предсказуемость: Одинаковое состояние всегда приводит к одинаковому UI
  • Простота отладки: Легко понять, какое состояние привело к текущему отображению

Паттерны условной отрисовки в приложении:

  • Условный рендер по состоянию загрузки: Показ спиннера во время запроса данных
  • Обработка ошибок: Отображение компонента ошибки при неудачном запросе
  • Условные элементы UI: Показ кнопок и элементов в зависимости от контекста
  • Списки с фильтрацией: Динамическое отображение элементов на основе условий

Преимущества декларативного подхода:

  • Читаемость: Код явно выражает намерения разработчика
  • Надежность: Меньше возможностей для ошибок при манипуляции DOM
  • Производительность: React оптимизирует обновления DOM
  • Тестируемость: Легко тестировать различные состояния компонента

В примере показана последовательная проверка состояний: сначала loading, затем error, и наконец успешное отображение данных. Это типичный паттерн для асинхронных операций в React приложениях.

// src/components/__generate/Doctor/Doctor/DoctorList.tsx
if (loading) return (<Spin tip="Loading..." />);
if (queryError) {
return (<ErrorModal error={queryError} setError={setError} />)
} else {
client.refetchQueries({include:["searchDoctor"]})
return (
<Space direction='vertical'>
{selectedDoctor === undefined && <Button
onClick={() => {
navigate(`/DoctorAgg/Doctor/Create/${filterStr}`)
}}
>
Create
</Button>}
<Table
columns={columns}
dataSource={mapToView(elemList)}
onRow={(record) => ({ onClick: event => { if (setSelectedDoctor) setSelectedDoctor(record.id) } })}
/>
</Space>
)
}

7. Паттерн Provider для глобального состояния

Приложение использует паттерн Provider для предоставления Apollo Client всем дочерним компонентам, что обеспечивает доступ к GraphQL API на любом уровне вложенности. Паттерн Provider основан на React Context API и решает проблему "prop drilling" — необходимости передавать данные через множество промежуточных компонентов, которые сами эти данные не используют.

Ключевые концепции паттерна Provider:

  • Context API: Встроенный механизм React для передачи данных через дерево компонентов
  • Provider компонент: Предоставляет значение всем дочерним компонентам
  • Consumer или хуки: Позволяют дочерним компонентам получить доступ к значению
  • Избежание prop drilling: Данные доступны на любом уровне вложенности без промежуточной передачи

Архитектура Provider в приложении:

  1. ApolloProvider оборачивает все приложение и предоставляет Apollo Client
  2. Дочерние компоненты получают доступ к клиенту через Apollo хуки
  3. Автоматическое управление: Apollo автоматически управляет подпиской на обновления

Преимущества использования Provider:

  • Глобальная доступность: Данные доступны в любом компоненте приложения
  • Централизованное управление: Одно место для управления глобальным состоянием
  • Производительность: React оптимизирует ре-рендеры только для подписанных компонентов
  • Типобезопасность: TypeScript обеспечивает типизацию контекста

Альтернативы паттерну Provider:

  • Prop drilling: Передача данных через props (подходит для простых случаев)
  • Внешние библиотеки состояния: Redux, Zustand, Valtio для сложной логики состояния
  • Серверное состояние: Apollo Client, React Query для данных с сервера

В примере ApolloProvider делает GraphQL клиент доступным для всех компонентов, что позволяет им использовать хуки типа useQuery, useMutation без необходимости явной передачи клиента через props.

// src/App.tsx
if (apolloClient)
return (
<ApolloProvider client={apolloClient}>
<MainMenu />
</ApolloProvider>
)

return (<>{"Loading..."}</>)

8. Обработка событий и навигация

Современные React-приложения активно используют программную навигацию через хуки React Router. Обработчики событий передаются как функции и вызываются при взаимодействии пользователя. Обработка событий в React следует принципу синтетических событий, которые обеспечивают кроссбраузерную совместимость и дополнительные возможности.

Принципы обработки событий в React:

  • Синтетические события: React оборачивает нативные события для единообразного поведения
  • Функции как значения: Обработчики передаются как функции в JSX атрибутах
  • Автоматическое связывание: В функциональных компонентах контекст this не требуется
  • Предотвращение по умолчанию: Использование preventDefault() для отмены стандартного поведения

Типы событий в приложении медицинской клиники:

  • Click события: Обработка кликов по кнопкам, строкам таблицы, ссылкам
  • Change события: Отслеживание изменений в полях ввода форм
  • Submit события: Обработка отправки форм
  • Navigation события: Программная навигация между страницами

Навигация с React Router:

  • useNavigate хук: Программная навигация через JavaScript
  • Link компоненты: Декларативная навигация через JSX
  • Параметры маршрута: Передача данных через URL параметры
  • Query параметры: Дополнительная информация в строке запроса

Паттерны навигации в приложении:

  1. Условная навигация: Переход только при выполнении определенных условий
  2. Навигация с параметрами: Передача ID сущности через URL
  3. Программная навигация: Автоматический переход после успешных операций
  4. Навигация с состоянием: Передача дополнных данных через state

Преимущества такого подхода:

  • Предсказуемость: Ясная связь между действиями пользователя и поведением приложения
  • Отладка: Легко отследить поток событий и навигации
  • Доступность: Поддержка клавиатурной навигации и скрин-ридеров
  • SEO: Корректные URL для поисковых систем

В примере показано, как клик по строке таблицы может вызвать callback для выбора элемента, а useNavigate позволяет программно перейти к форме создания с передачей контекстных параметров.

// src/components/__generate/Doctor/Doctor/DoctorList.tsx
const navigate = useNavigate();

return (
<Table
columns={columns}
dataSource={mapToView(elemList)}
onRow={(record) => ({
onClick: event => {
if (setSelectedDoctor) setSelectedDoctor(record.id)
}
})}
/>
)

Эти примеры демонстрируют, как современные концепции React применяются в реальном приложении, обеспечивая масштабируемость, типобезопасность и удобство разработки.

Ant Design

Ant Design — это enterprise-класса UI библиотека для React, разработанная компанией Ant Financial. В проекте медицинской клиники используется Ant Design версии 4.13.0 в качестве основной UI библиотеки, что обеспечивает профессиональный внешний вид и богатую функциональность компонентов.

Основные преимущества Ant Design

Готовые компоненты высокого качества: Ant Design предоставляет более 50 готовых компонентов, покрывающих все потребности корпоративных приложений — от простых кнопок до сложных таблиц с сортировкой и фильтрацией.

Единый дизайн-язык: Все компонены следуют единым принципам дизайна, что обеспечивает визуальную согласованность интерфейса без дополнительных усилий от разработчика.

TypeScript поддержка: Библиотека написана на TypeScript и предоставляет полную типизацию для всех компонентов и их API.

Интернационализация: Встроенная поддержка множества языков и локализаций, включая русский язык.

Применение в приложении медицинской клиники

1. Layout и навигация

В приложении активно используются компоненты Layout и Menu для создания общей структуры интерфейса:

// src/components/__generate/MainMenu.tsx
import { Layout, Menu } from 'antd';

const { Header } = Layout;

export const MainMenu: FC = () => {
return (
<BrowserRouter>
<Layout>
<Header>
<Menu mode='horizontal' >
<Menu.Item key='DoctorAgg'>
<Link to={'/DoctorAgg'}>DoctorAgg</Link>
</Menu.Item>
// ... другие пункты меню
</Menu>
</Header>
// ... маршруты
</Layout>
</BrowserRouter>
)
}

2. Формы и элементы ввода

Компоненты Form, Input, Button, Space и Modal используются для создания сложных форм ввода данных:

// src/components/__generate/Doctor/Doctor/DoctorCreateForm.tsx
import { DatePicker, Checkbox, Form, Input, Select, Space, Button, Modal } from 'antd';

export const DoctorCreateForm: FC<FormProps> = ({ inputParameters, changeInputParameters }) => {
return (
<>
<Form>
<Form.Item label="doctorType">
<Space>
<Input
value={inputParameters.doctorType}
onChange={e => changeInputParameters({ doctorType: e.target.value})}
/>
<Button onClick={() => setSelectedDoctorTypeId(null)}>{"..."}</Button>
</Space>
</Form.Item>
</Form>

<Modal
visible={selectedDoctorTypeId === null}
footer={null}
width="90%"
onCancel={() => setSelectedDoctorTypeId(undefined)}
>
<DoctorTypeList selectedDoctorType={selectedDoctorTypeId} />
</Modal>
</>
)
}

3. Отображение данных и индикаторы состояния

Для отображения списков данных и индикации состояния загрузки используются Table, Spin и другие компоненты:

// src/components/__generate/Doctor/Doctor/DoctorList.tsx
import { Button, Space, Spin, Table } from 'antd';

if (loading) return (<Spin tip="Loading..." />);

return (
<Space direction='vertical'>
<Button onClick={() => navigate(`/DoctorAgg/Doctor/Create/${filterStr}`)}>
Create
</Button>
<Table
columns={columns}
dataSource={mapToView(elemList)}
onRow={(record) => ({ onClick: event => { setSelectedDoctor(record.id) } })}
/>
</Space>
)

4. Модальные окна и обработка ошибок

Modal компонент используется для отображения ошибок и диалоговых окон:

// src/components/basic/ErrorModal.tsx
import { Button, Modal } from 'antd';

const ErrorModal: React.FC<Props> = ({ error, setError }) => {
return (
<Modal footer={null} visible={true} onCancel={() => setError(null)}>
{error.message}
</Modal>
)
};

Интеграция с проектом

Подключение стилей

В главном файле приложения подключаются стили Ant Design:

// src/App.tsx
import 'antd/dist/antd.css';

Зависимости проекта

В package.json указаны необходимые зависимости:

  • antd: "4.13.0" — основная библиотека компонентов
  • @ant-design/icons: "^5.1.4" — набор иконок для интерфейса

Архитектурные решения

Консистентность интерфейса: Использование единой UI библиотеки обеспечивает согласованный пользовательский опыт по всему приложению.

Быстрая разработка: Готовые компоненты значительно ускоряют процесс создания интерфейса, особенно в контексте автоматической генерации кода.

Адаптивность: Компоненты Ant Design поддерживают адаптивный дизайн и корректно отображаются на различных устройствах.

Доступность: Библиотека следует стандартам WAI-ARIA, обеспечивая доступность для пользователей с ограниченными возможностями.

Ant Design в проекте медицинской клиники выступает как надежная основа для создания профессионального корпоративного интерфейса, позволяя сосредоточиться на бизнес-логике вместо создания базовых UI компонентов.

Заключение

Проект React-приложения медицинской клиники демонстрирует эффективный подход к автоматической генерации типобезопасного React-приложения на основе GraphQL-схемы. Используя принципы кодогенерации, проект значительно ускоряет разработку и обеспечивает согласованность кода.

Сгенерированное приложение демонстрирует современные практики разработки с использованием TypeScript и React:

  • Строгая типизация всех компонентов и данных
  • Функциональное программирование с использованием хуков
  • Компонентная архитектура с четким разделением ответственности
  • Декларативный подход к построению пользовательского интерфейса
  • Интеграция с GraphQL API через Apollo Client

Такой подход позволяет сосредоточиться на бизнес-логике приложения, вместо написания типовых компонентов и запросов вручную. В следующем разделе учебного курса мы рассмотрим интеграцию механизмов авторизации и расширение функциональности проекта с использованием AI-инструментов.

Ссылки